/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../toolkit/display.h"
#include "../toolkit/prop.h"
#include "../toolkit/xml.h"
#include "focus.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "debug.h"
#include "screen.h"
#include "focus_cycle.h"
#include "misc.h"
#include "wm.h"
#include "event.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "group.h"
#define FOCUS_INDICATOR_WIDTH 6
struct wm_client *focus_client = NULL;
GList *focus_order = NULL;
void focus_startup(void)
{
	focus_nothing();
}

void focus_shutdown(void)
{
	XSetInputFocus(t_display, PointerRoot, RevertToNone, CurrentTime);
}

static void push_to_top(struct wm_client *client)
{
	struct wm_client *p;
	if(client->modal && (p = client_direct_parent(client)))
		push_to_top(p);
	focus_order = g_list_remove(focus_order, client);
	focus_order = g_list_prepend(focus_order, client);
}

void focus_set_client(struct wm_client *client)
{
	wm_debug_type(WM_DEBUG_FOCUS, "focus_set_client 0x%lx", client ? client->window : 0);
	if(focus_client == client)
		return;
	screen_install_colormap(focus_client, FALSE);
	screen_install_colormap(client, TRUE);
	focus_client = client;
	if(client != NULL) {
		push_to_top(client);
		client_hilite(client, FALSE);
		focus_cycle_reorder();
	}
	event_reset_user_time();
}

static struct wm_client *fallback_target(gboolean allow_refocus, gboolean allow_pointer, gboolean allow_omnipresent,
	struct wm_client *old)
{
	GList *it;
	struct wm_client *c;
	wm_debug_type(WM_DEBUG_FOCUS, "trying pointer stuff");
	if(allow_pointer && config_focus_follow)
		if((c = client_under_pointer()) && (allow_refocus || client_focus_target(c) != old) && (client_normal(c)
				&& client_focus(c))) {
			wm_debug_type(WM_DEBUG_FOCUS, "found in pointer stuff");
			return c;
		}
	wm_debug_type(WM_DEBUG_FOCUS, "trying the focus order");
	for(it = focus_order; it; it = g_list_next(it)) {
		c = it->data;
		if((allow_omnipresent || c->desktop == screen_desktop)
			&& focus_valid_target(c, screen_desktop, TRUE, FALSE, TRUE, FALSE, FALSE)
			&& (allow_refocus || client_focus_target(c) != old) && client_focus(c)) {
			wm_debug_type(WM_DEBUG_FOCUS, "found in focus order");
			return c;
		}
	}
	wm_debug_type(WM_DEBUG_FOCUS, "trying a desktop window");
	for(it = focus_order; it; it = g_list_next(it)) {
		c = it->data;
		if(focus_valid_target(c, screen_desktop, TRUE, FALSE, TRUE, TRUE, FALSE) && (allow_refocus
				|| client_focus_target(c) != old) && client_focus(c)) {
			wm_debug_type(WM_DEBUG_FOCUS, "found a desktop window");
			return c;
		}
	}
	return NULL;
}

struct wm_client *focus_fallback(gboolean allow_refocus, gboolean allow_pointer, gboolean allow_omnipresent,
	gboolean focus_lost)
{
	struct wm_client *new;
	struct wm_client *old = focus_client;
	if(focus_lost)
		focus_nothing();
	new = fallback_target(allow_refocus, allow_pointer, allow_omnipresent, old);
	if(new)
		new = client_focus_target(new);
	return new;
}

void focus_nothing(void)
{
	focus_set_client(NULL);
	XSetInputFocus(t_display, screen_support_win, RevertToPointerRoot, event_time());
}

void focus_order_add_new(struct wm_client *c)
{
	g_assert(!g_list_find(focus_order, c));
	focus_order = g_list_insert(focus_order, c, 1);
	focus_cycle_addremove(c, TRUE);
}

void focus_order_remove(struct wm_client *c)
{
	focus_order = g_list_remove(focus_order, c);
	focus_cycle_addremove(c, TRUE);
}

void focus_order_to_top(struct wm_client *c)
{
	focus_order = g_list_remove(focus_order, c);
	focus_order = g_list_prepend(focus_order, c);
	focus_cycle_reorder();
}

void focus_order_to_bottom(struct wm_client *c)
{
	focus_order = g_list_remove(focus_order, c);
	GList *it;
	for(it = focus_order; it; it = g_list_next(it));
	focus_order = g_list_insert_before(focus_order, it, c);
	focus_cycle_reorder();
}

static gboolean target_has_siblings(struct wm_client *ft, gboolean all_desktops)
{
	GSList *it;
	if(!ft->group)
		return FALSE;
	for(it = ft->group->members; it; it = g_slist_next(it)) {
		struct wm_client *c = it->data;
		if(c != ft && c->type == WM_CLIENT_TYPE_NORMAL
			&& focus_valid_target(c, screen_desktop, TRUE, all_desktops, TRUE, FALSE, FALSE)) {
			return TRUE;
		}
	}
	return FALSE;
}

gboolean focus_valid_target(struct wm_client *ft, guint desktop, gboolean helper_windows, gboolean all_desktops,
	gboolean nonhilite_windows, gboolean desktop_windows, gboolean user_request)
{
	gboolean ok = FALSE;
	if(!ft->managed)
		return FALSE;
	ok = (all_desktops || ft->desktop == desktop || ft->desktop == DESKTOP_ALL);
	ok = ok && (nonhilite_windows || ft->demands_attention);
	ok = ok && (ft->can_focus || ft->focus_notify);
	if(desktop_windows)
		ok = ok && (desktop_windows && ft->type == WM_CLIENT_TYPE_DESKTOP);
	else if(!ft->modal)
		ok = ok && ((client_normal(ft) && !client_helper(ft)) || (client_helper(ft) && ((focus_client
						&& ft->group == focus_client->group && helper_windows)
					|| !target_has_siblings(ft, all_desktops))));
	ok = ok && (!ft->skip_taskbar || (ft->modal || user_request || ft->demands_attention
			|| ft->type == WM_CLIENT_TYPE_DIALOG)); {
		struct wm_client *cft = client_focus_target(ft);
		ok = ok && (ft == cft
			|| !focus_valid_target(cft, screen_desktop, TRUE, all_desktops, nonhilite_windows, desktop_windows,
			FALSE));
	} return ok;
}
